//////////////////////////////////////////////////////////////////////////
//
// This file is a part of APNS provider project.
// Copyright (c) Vladimir Gumenuk, 2013-2014.
//
// PROJECT:         APNS provider
// FILE:            APNSClient.cpp
// AUTHOR:          Vladimir Gumenuk
// DATE:            22-Dec-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "APNSClient.h"

// example: http://blog.toshsoft.de/index.php?/archives/3-Sending-Apple-Push-Notifications-APN-in-C-Updated-Using-CA-Cert.html

#pragma comment(lib, "Crypt32.Lib")

HRESULT __stdcall CreateAPNSClient( IAPNSClient **ppOut )
{
    return CAPNSClient::Create( ppOut );
}

HRESULT CAPNSClient::Create( IAPNSClient **ppOut )
{
    if( !ppOut )
        return E_POINTER;

    typedef CComObject<CAPNSClient> TAPNSClient;
    TAPNSClient *p = NULL;
    HRESULT hr = TAPNSClient::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();
        hr = p->QueryInterface( IID_IAPNSClient, (void**)ppOut );
        p->Release();
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////
// CAPNSClient
CAPNSClient::CAPNSClient()
{
    m_bCredsInitialized = FALSE;
    m_bContextInitialized = FALSE;
    m_hCertStore   = NULL;
    m_hSecurity    = NULL;
    m_pSSPI        = NULL;
    m_dwProtocol   = SP_PROT_TLS1; // SP_PROT_TLS1; SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default
    m_aiKeyExch    = 0;            // CALG_DH_EPHEM; CALG_RSA_KEYX;
    m_pIoBuffer    = NULL;
    m_dwIoBufLen   = 0;
}

CAPNSClient::~CAPNSClient()
{
}

static void DisplayCertificateChain( PCCERT_CONTEXT pServerCert, BOOL fLocal )
{
    TCHAR szName[1024];

    // display leaf name
    if( !CertNameToStr( pServerCert->dwCertEncodingType, &pServerCert->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, sizeof(szName) ) )
    {
        LOG1( _T("**** Error 0x%08X building subject name\n"), GetLastError() );
    }

    if( fLocal ) {
        LOG1( _T("\nClient subject: %s\n"), szName );
    } else { 
        LOG1( _T("\nServer subject: %s\n"), szName );
    }

    if( !CertNameToStr( pServerCert->dwCertEncodingType, &pServerCert->pCertInfo->Issuer, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, sizeof(szName) ) )
    {
        LOG1( _T("**** Error 0x%08X building issuer name\n"), GetLastError() );
    }

    if( fLocal ) {
        LOG1( _T("Client issuer: %s\n"), szName );
    } else {
        LOG1( _T("Server issuer: %s\n\n"), szName );
    }

    PCCERT_CONTEXT pCurrentCert = pServerCert;
    while( pCurrentCert != NULL ) // display certificate chain
    {
        DWORD dwVerificationFlags = 0;
        PCCERT_CONTEXT pIssuerCert = CertGetIssuerCertificateFromStore( pServerCert->hCertStore, pCurrentCert, NULL, &dwVerificationFlags );
        if( pIssuerCert == NULL )
        {
            if( pCurrentCert != pServerCert ) CertFreeCertificateContext( pCurrentCert );
            break;
        }

        if( !CertNameToStr( pIssuerCert->dwCertEncodingType, &pIssuerCert->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, sizeof(szName) ) )
        { 
            LOG1( _T("**** Error 0x%08X building subject name\n"), GetLastError() ); 
        }

        LOG1( _T("CA subject: %s\n"), szName );

        if( !CertNameToStr( pIssuerCert->dwCertEncodingType, &pIssuerCert->pCertInfo->Issuer, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, sizeof(szName) ) )
        {
            LOG1( _T("**** Error 0x%08X building issuer name\n"), GetLastError() );
        }

        LOG1( _T("CA issuer: %s\n\n"), szName );

        if( pCurrentCert != pServerCert )
            CertFreeCertificateContext( pCurrentCert );
        pCurrentCert = pIssuerCert;
        pIssuerCert = NULL;
    }
}

void CAPNSClient::DisplayConnectionInfo( CtxtHandle *phContext )
{
    SecPkgContext_ConnectionInfo ConnectionInfo;
    HRESULT hr = m_pSSPI->QueryContextAttributes( phContext, SECPKG_ATTR_CONNECTION_INFO, (PVOID)&ConnectionInfo );
    if( FAILED( hr ) )
    {
        LOG1( _T("Error 0x%08X querying connection info\n"), hr );
        return;
    }

    LOG1( _T("\n") );

    switch( ConnectionInfo.dwProtocol )
    {
    case SP_PROT_TLS1_CLIENT:   LOG1( _T("Protocol: TLS1\n") ); break;
    case SP_PROT_SSL3_CLIENT:   LOG1( _T("Protocol: SSL3\n") ); break;
    case SP_PROT_PCT1_CLIENT:   LOG1( _T("Protocol: PCT\n") ); break;
    case SP_PROT_SSL2_CLIENT:   LOG1( _T("Protocol: SSL2\n") ); break;
    default:                    LOG1( _T("Protocol: 0x%08X\n"), ConnectionInfo.dwProtocol );
    }

    switch( ConnectionInfo.aiCipher )
    {
    case CALG_RC4:      LOG1( _T("Cipher: RC4\n") ); break;
    case CALG_3DES:     LOG1( _T("Cipher: Triple DES\n") ); break;
    case CALG_RC2:      LOG1( _T("Cipher: RC2\n") );    break;
    case CALG_DES: 
    case CALG_CYLINK_MEK:LOG1( _T("Cipher: DES\n") );   break;
    case CALG_SKIPJACK: LOG1( _T("Cipher: Skipjack\n") );   break;
    default:            LOG1( _T("Cipher: 0x%08X\n"), ConnectionInfo.aiCipher );
    }

    LOG1( _T("Cipher strength: %d\n"), ConnectionInfo.dwCipherStrength );

    switch( ConnectionInfo.aiHash )
    {
    case CALG_MD5:  LOG1( _T("Hash: MD5\n") );  break;
    case CALG_SHA:  LOG1( _T("Hash: SHA\n") );  break;
    default:        LOG1( _T("Hash: 0x%08X\n"), ConnectionInfo.aiHash );
    }

    LOG1( _T("Hash strength: %d\n"), ConnectionInfo.dwHashStrength );

    switch( ConnectionInfo.aiExch )
    {
    case CALG_RSA_KEYX: 
    case CALG_RSA_SIGN:     LOG1( _T("Key exchange: RSA\n") );  break;
    case CALG_KEA_KEYX:     LOG1( _T("Key exchange: KEA\n") );  break;
    case CALG_DH_EPHEM:     LOG1( _T("Key exchange: DH Ephemeral\n") ); break;
    default:                LOG1( _T("Key exchange: 0x%08X\n"), ConnectionInfo.aiExch ); break;
    }

    LOG1( _T("Key exchange strength: %d\n"), ConnectionInfo.dwExchStrength);
}

BOOL CAPNSClient::LoadSecurityLibrary() // load SSPI.DLL, set up a special table - PSecurityFunctionTable
{
    BOOL bRet = FALSE;
    INIT_SECURITY_INTERFACE pInitSecurityInterface;
    OSVERSIONINFO VerInfo;
    TCHAR lpszDLL[MAX_PATH];

    //  Find out which security DLL to use, depending on whether we are on Win2K, NT or Win9x
    VerInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if( !GetVersionEx( &VerInfo ) ) 
        return bRet;

    if ( VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT  &&  VerInfo.dwMajorVersion == 4 )
    {
        _tcscpy( lpszDLL, NT4_DLL_NAME ); // NT4_DLL_NAME TEXT("Security.dll")
    }
    else if( VerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT )
    {
        _tcscpy( lpszDLL, DLL_NAME ); // DLL_NAME TEXT("Secur32.dll")
    } else {
        LOG1( _T("System not recognized\n") ); 
        return bRet; 
    }

    if( m_hSecurity = LoadLibrary( lpszDLL ) ) //  Load Security DLL
    {
        if( pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress( m_hSecurity, "InitSecurityInterfaceW" ) )
        {
            if( m_pSSPI = pInitSecurityInterface() ) // init interface
            {
                bRet = TRUE;
            } else {
                LOG1( _T("Error 0x%08X reading security interface.\n"), GetLastError() ); 
            }
        } else {
            LOG1( _T("Error 0x%08X reading InitSecurityInterface entry point.\n"), GetLastError() ); 
        }
    } else {
        LOG1( _T("Error 0x%08X loading %s.\n"), GetLastError(), lpszDLL );
    }

    return bRet;
}

void CAPNSClient::UnloadSecurityLibrary()
{
    if( m_hSecurity )
    {
        FreeLibrary( m_hSecurity );
        m_hSecurity = NULL;

        m_pSSPI = NULL;
    }
}

static HRESULT VerifyServerCertificate( PCCERT_CONTEXT pServerCert, LPCTSTR pszServerName, DWORD dwCertFlags )
{
    if( !pServerCert || pszServerName == NULL || _tcslen(pszServerName) == 0 )
        return SEC_E_WRONG_PRINCIPAL;

    HRESULT hr = E_FAIL;

    LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE };
    DWORD cUsages      = sizeof(rgszUsages) / sizeof(LPSTR);
    
    // Build certificate chain.
    CERT_CHAIN_PARA ChainPara;
    ZeroMemory( &ChainPara, sizeof( ChainPara ) );
    ChainPara.cbSize                                    = sizeof(ChainPara);
    ChainPara.RequestedUsage.dwType                     = USAGE_MATCH_TYPE_OR;
    ChainPara.RequestedUsage.Usage.cUsageIdentifier     = cUsages;
    ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;

    PCCERT_CHAIN_CONTEXT  pChainContext = NULL;
    if( CertGetCertificateChain( NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext ) )
    {
        // Validate certificate chain.
        CComBSTR bsName( pszServerName );

        HTTPSPolicyCallbackData  polHttps;
        ZeroMemory( &polHttps, sizeof(HTTPSPolicyCallbackData) );
        polHttps.cbStruct           = sizeof(HTTPSPolicyCallbackData);
        polHttps.dwAuthType         = AUTHTYPE_SERVER;
        polHttps.fdwChecks          = dwCertFlags;
        polHttps.pwszServerName     = bsName.m_str;
                
        CERT_CHAIN_POLICY_PARA   PolicyPara;
        ZeroMemory( &PolicyPara, sizeof(PolicyPara) );
        PolicyPara.cbSize            = sizeof(PolicyPara);
        PolicyPara.pvExtraPolicyPara = &polHttps;

        CERT_CHAIN_POLICY_STATUS PolicyStatus;
        ZeroMemory( &PolicyStatus, sizeof(PolicyStatus) );
        PolicyStatus.cbSize = sizeof( PolicyStatus );

        if( CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus ) )
        {
            if( PolicyStatus.dwError )
            {
                hr = PolicyStatus.dwError;
                ShowVerifyTrustError( hr ); 
            } else
                hr = SEC_E_OK;
        } else {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            LOG1( _T("Error 0x%08X returned by CertVerifyCertificateChainPolicy!\n"), hr );
        }
    } else {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        LOG1( _T("Error 0x%08X returned by CertGetCertificateChain!\n"), hr );
    }
    
    if( pChainContext )
        CertFreeCertificateChain( pChainContext );

    return hr;
}

HRESULT CAPNSClient::CreateCredentials( LPCTSTR pszUser, PCredHandle phCreds )   
{
    PCCERT_CONTEXT pCertContext = NULL;
    
    if( m_hCertStore == NULL )
    {
        m_hCertStore = CertOpenSystemStore( 0, _T("MY") ); // Open the "MY" certificate store, where IE stores client certificates. Windows maintains 4 stores -- MY, CA, ROOT, SPC. 
        if( !m_hCertStore )
        {
            LOG1( _T("**** Error 0x%08X returned by CertOpenSystemStore\n"), GetLastError() );
            return SEC_E_NO_CREDENTIALS;
        }
    }
    
    if( pszUser ) // If a user name is specified, then attempt to find a client certificate. Otherwise, just create a NULL credential.
    {
        // Find client certificate. Note that this sample just searches for a 
        // certificate that contains the user name somewhere in the subject name.
        // A real application should be a bit less casual.
        pCertContext = CertFindCertificateInStore( m_hCertStore,   // hCertStore
            X509_ASN_ENCODING,                                     // dwCertEncodingType
            0,                                                     // dwFindFlags
            CERT_FIND_SUBJECT_STR,                                 // dwFindType
            pszUser,                                               // *pvFindPara
            NULL );                                                // pPrevCertContext

        if( pCertContext == NULL )
        {
            LOG1( _T("**** Error 0x%08X returned by CertFindCertificateInStore\n"), GetLastError());
            if( GetLastError() == CRYPT_E_NOT_FOUND ) LOG1( _T("CRYPT_E_NOT_FOUND - property doesn't exist\n") );
            return SEC_E_NO_CREDENTIALS;
        }
    }
    
    // Build Schannel credential structure. Currently, this sample only specifies the protocol to be used (and optionally the certificate, 
    // of course). Real applications may wish to specify other parameters as well.
    ZeroMemory( &m_SchannelCred, sizeof( m_SchannelCred ) );

    m_SchannelCred.dwVersion  = SCHANNEL_CRED_VERSION;
    if( pCertContext )
    {
        m_SchannelCred.cCreds = 1;
        m_SchannelCred.paCred = &pCertContext;
    }

    m_SchannelCred.grbitEnabledProtocols = m_dwProtocol;
        
    ALG_ID rgbSupportedAlgs[16];
    DWORD  cSupportedAlgs = 0;
    if( m_aiKeyExch )
        rgbSupportedAlgs[cSupportedAlgs++] = m_aiKeyExch;

    if( cSupportedAlgs )
    {
        m_SchannelCred.cSupportedAlgs    = cSupportedAlgs;
        m_SchannelCred.palgSupportedAlgs = rgbSupportedAlgs;
    }

    m_SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;

    // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
    // this sample verifies the server certificate manually. 
    // Applications that expect to run on WinNT, Win9x, or WinME 
    // should specify this flag and also manually verify the server
    // certificate. Applications running on newer versions of Windows can
    // leave off this flag, in which case the InitializeSecurityContext
    // function will validate the server certificate automatically.
    m_SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
    
    TimeStamp  tsExpiry;
    // Create an SSPI credential.
    HRESULT hr = m_pSSPI->AcquireCredentialsHandle( NULL,                 // Name of principal    
        UNISP_NAME,           // Name of package
        SECPKG_CRED_OUTBOUND, // Flags indicating use
        NULL,                 // Pointer to logon ID
        &m_SchannelCred,      // Package specific data
        NULL,                 // Pointer to GetKey() func
        NULL,                 // Value to pass to GetKey()
        phCreds,              // (out) Cred Handle
        &tsExpiry );          // (out) Lifetime (optional)

    if( FAILED( hr ) ) {
        LOG1( _T("**** Error 0x%08X returned by AcquireCredentialsHandle\n"), hr );
    }

    // cleanup: Free the certificate context. Schannel has already made its own copy.
    if( pCertContext )
        CertFreeCertificateContext( pCertContext );

    return hr;
}

STDMETHODIMP CAPNSClient::Connect( IGMSNetwork *pNet, LPCTSTR lpszCertSubject, LPCTSTR lpszServerName, DWORD dwPortNumber )
{
    if( !pNet || !lpszCertSubject || !lpszServerName )
        return E_POINTER;
    if( m_spNetClient )
        return E_UNEXPECTED;

    m_spNetwork = pNet;

    HRESULT hr = m_spNetwork->CreateNetClient( &m_spNetClient );
    if( FAILED( hr ) )
        return hr;

    if( !LoadSecurityLibrary() )
    {
        LOG1( _T("Error initializing the security library\n") );
        return HRESULT_FROM_WIN32( GetLastError() );
    } 
    LOG1( _T("----- SSPI Initialized\n") );

    if( FAILED( hr = CreateCredentials( lpszCertSubject, &m_hClientCreds ) ) )
    {
        LOG1( _T("Error creating credentials, hr: 0x%08X\n"), hr ); 
        return hr;
    }
    m_bCredsInitialized = TRUE;
    LOG1( _T("----- Credentials Initialized\n") );
    
    do
    {   // we start from physical connection to server...
        if( SUCCEEDED( hr = m_spNetClient->Connect( lpszServerName, dwPortNumber ) ) )
        {
            SecBuffer  ExtraData;

            if( PerformClientHandshake( &m_hClientCreds, lpszServerName, &m_hContext, &ExtraData ) )
            {
                LOG1( _T("Error performing handshake\n") ); 
                break;
            }
            m_bContextInitialized = TRUE; 
            LOG1( _T("----- Client Handshake Performed\n") );
            
            // Authenticate server's credentials. Get server's certificate.
            PCCERT_CONTEXT pRemoteCertContext = NULL;
            if( FAILED( hr = m_pSSPI->QueryContextAttributes( &m_hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext ) ) ) 
            {
                LOG1( _T("Error 0x%08X querying remote certificate\n"), hr );
                break; 
            } 
            LOG1( _T("----- Server Credentials Authenticated \n") );
            
            DisplayCertificateChain( pRemoteCertContext, FALSE );
            LOG1( _T("----- Certificate Chain Displayed \n") );

            // Attempt to validate server certificate.
            if( FAILED( hr = VerifyServerCertificate( pRemoteCertContext, lpszServerName, 0 ) ) )
            {
                LOG1( _T("**** Error 0x%08X authenticating server credentials!\n"), hr);
                break; // means that we probably connected to "man in the middle".
            }
            LOG1( _T("----- Server Certificate Verified\n") );
            
            CertFreeCertificateContext( pRemoteCertContext ); // Free the server certificate context.
            pRemoteCertContext = NULL; //
            LOG1( _T("----- Server certificate context released\n") );
            
            DisplayConnectionInfo( &m_hContext ); // Display connection info. 
            LOG1( _T("----- Secure Connection Info\n") );
            
            // initialize input/output buffer
            if( FAILED( hr = m_pSSPI->QueryContextAttributes( &m_hContext, SECPKG_ATTR_STREAM_SIZES, &m_StreamSizes ) ) )
            { 
                LOG1( _T("**** Error 0x%08X reading SECPKG_ATTR_STREAM_SIZES\n"), hr ); 
                break;
            }

            // Create a io buffer. 
            m_dwIoBufLen = m_StreamSizes.cbHeader  +  m_StreamSizes.cbMaximumMessage  +  m_StreamSizes.cbTrailer;
            m_pIoBuffer  = (LPBYTE)LocalAlloc( LMEM_FIXED, m_dwIoBufLen );
            if( !m_pIoBuffer )
            { 
                LOG1( _T("**** Out of memory\n") ); hr = E_OUTOFMEMORY;
                break;
            } else {
                LOG1( _T("Buffer allocated, header: %d, Message: %d, Trailer: %d, Total: %d bytes" ), m_StreamSizes.cbHeader, m_StreamSizes.cbMaximumMessage, m_StreamSizes.cbTrailer, m_dwIoBufLen );
            }
        }
    } while( 0 );

    return hr;
}

STDMETHODIMP CAPNSClient::Disconnect()
{
    DWORD dwType = SCHANNEL_SHUTDOWN; // Notify schannel that we are about to close the connection.

    SecBufferDesc OutBuffer;
    SecBuffer     OutBuffers[1];

    OutBuffers[0].pvBuffer   = &dwType;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = sizeof(dwType);
    OutBuffer.cBuffers       = 1;
    OutBuffer.pBuffers       = OutBuffers;
    OutBuffer.ulVersion      = SECBUFFER_VERSION;

    HRESULT hr = m_pSSPI->ApplyControlToken( &m_hContext, &OutBuffer );
    if( FAILED( hr ) )
    {
        LOG1( _T("**** Error 0x%08X returned by ApplyControlToken\n"), hr ); goto cleanup;
    }

    // Build an SSL close notify message.
    DWORD dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;

    OutBuffers[0].pvBuffer   = NULL;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = 0;

    OutBuffer.cBuffers  = 1;
    OutBuffer.pBuffers  = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    SEC_WCHAR szHostName[MAX_PATH];
    m_spNetClient->GetRemoteHostName( szHostName );

    DWORD     dwSSPIOutFlags;
    TimeStamp tsExpiry;
    if( FAILED( hr = m_pSSPI->InitializeSecurityContext( &m_hClientCreds, &m_hContext, szHostName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, &m_hContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry ) ) )
    {
        LOG1( _T("**** Error 0x%08X returned by InitializeSecurityContext\n"), hr ); goto cleanup; 
    }

    LPBYTE pbMessage = (LPBYTE)OutBuffers[0].pvBuffer;
    DWORD dwMsgLen = OutBuffers[0].cbBuffer;
    
    // Send the close notify message to the server.
    if( pbMessage != NULL && dwMsgLen != 0 )
    {
        DWORD cbData = m_spNetClient->SendRaw( pbMessage, dwMsgLen );
        if( cbData == SOCKET_ERROR )
        {
            goto cleanup;
        }
        LOG1( _T("Sending Close Notify\n") );
        LOG1( _T("%d bytes of handshake data sent\n"), cbData);
        m_pSSPI->FreeContextBuffer( pbMessage ); // Free output buffer.
    }

cleanup:
    if( m_bContextInitialized )
    {
        m_pSSPI->DeleteSecurityContext( &m_hContext );
        m_bContextInitialized = FALSE;
    }

    if( m_bCredsInitialized )
    {
        m_pSSPI->FreeCredentialsHandle( &m_hClientCreds );
        m_bCredsInitialized = FALSE;
    }

    if( m_hCertStore )
    {
        CertCloseStore( m_hCertStore, 0 );
        m_hCertStore = NULL;
    }

    UnloadSecurityLibrary();

    m_spNetClient->Disconnect();
    m_spNetClient.Release();

    if( m_pIoBuffer )
    {
        LocalFree( m_pIoBuffer );
        m_pIoBuffer = NULL;
    }

    m_dwIoBufLen = 0;

    return hr;
}

void CAPNSClient::GetNewClientCredentials( CredHandle *phCreds, CtxtHandle *phContext )
{
    CredHandle                        hCreds;
    SecPkgContext_IssuerListInfoEx    IssuerListInfo;
    PCCERT_CHAIN_CONTEXT              pChainContext = NULL;
    CERT_CHAIN_FIND_BY_ISSUER_PARA    FindByIssuerPara;
    PCCERT_CONTEXT                    pCertContext = NULL;
    TimeStamp                         tsExpiry;
    HRESULT                   hr;
    
    // Read list of trusted issuers from schannel.
    if( FAILED( hr = m_pSSPI->QueryContextAttributes( phContext, SECPKG_ATTR_ISSUER_LIST_EX, (PVOID)&IssuerListInfo ) ) )
    {
        LOG1( _T("Error 0x%08X querying issuer list info\n"), hr); 
        return;
    }

    // Enumerate the client certificates.
    ZeroMemory( &FindByIssuerPara, sizeof( FindByIssuerPara ) );
    FindByIssuerPara.cbSize = sizeof( FindByIssuerPara );
    FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
    FindByIssuerPara.dwKeySpec = 0;
    FindByIssuerPara.cIssuer   = IssuerListInfo.cIssuers;
    FindByIssuerPara.rgIssuer  = IssuerListInfo.aIssuers;

    while( TRUE )
    {   // Find a certificate chain.
        pChainContext = CertFindChainInStore( m_hCertStore, X509_ASN_ENCODING, 0, CERT_CHAIN_FIND_BY_ISSUER, &FindByIssuerPara, pChainContext );
        if( pChainContext == NULL ) { LOG1( _T("Error 0x%08X finding cert chain\n"), GetLastError()); break; }

        LOG1( _T("\ncertificate chain found\n") );

        // Get pointer to leaf certificate context.
        pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;

        // Create schannel credential.
        m_SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
        m_SchannelCred.cCreds = 1;
        m_SchannelCred.paCred = &pCertContext;

        if( FAILED( hr = m_pSSPI->AcquireCredentialsHandle(  NULL,                   // Name of principal
            UNISP_NAME,             // Name of package
            SECPKG_CRED_OUTBOUND,   // Flags indicating use
            NULL,                   // Pointer to logon ID
            &m_SchannelCred,        // Package specific data
            NULL,                   // Pointer to GetKey() func
            NULL,                   // Value to pass to GetKey()
            &hCreds,                // (out) Cred Handle
            &tsExpiry ) ) )         // (out) Lifetime (optional)
        {
            LOG1( _T("**** Error 0x%08X returned by AcquireCredentialsHandle\n"), hr );
            continue;
        }

        LOG1( _T("\nnew schannel credential created\n") );

        m_pSSPI->FreeCredentialsHandle( phCreds ); // Destroy the old credentials.

        *phCreds = hCreds;
    }
}

HRESULT CAPNSClient::ClientHandshakeLoop( PCredHandle phCreds, CtxtHandle *phContext, BOOL bDoInitialRead, SecBuffer *pExtraData )
{
    SecBufferDesc   OutBuffer, InBuffer;
    SecBuffer       InBuffers[2], OutBuffers[1];
    DWORD           dwSSPIOutFlags, cbData;
    TimeStamp       tsExpiry;
    
    DWORD dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;

    // Allocate data buffer.
    LPBYTE IoBuffer = (PUCHAR)LocalAlloc( LMEM_FIXED, IO_BUFFER_SIZE );
    if( IoBuffer == NULL )
    {
        LOG1( _T("**** Out of memory (1)\n") );
        return SEC_E_INTERNAL_ERROR;
    }
    DWORD cbIoBuffer = 0;

    BOOL fDoRead = bDoInitialRead;
    
    // Loop until the handshake is finished or an error occurs.
    HRESULT hr = SEC_I_CONTINUE_NEEDED;

    while( hr == SEC_I_CONTINUE_NEEDED || hr == SEC_E_INCOMPLETE_MESSAGE || hr == SEC_I_INCOMPLETE_CREDENTIALS ) 
    {
        if( 0 == cbIoBuffer || hr == SEC_E_INCOMPLETE_MESSAGE ) // Read data from server.
        {
            if( fDoRead )
            {
                cbData = m_spNetClient->ReceiveRaw( IoBuffer + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer );
                if( cbData == SOCKET_ERROR )
                {
                    LOG1( _T("**** Error %d reading data from server\n"), WSAGetLastError());
                    hr = SEC_E_INTERNAL_ERROR;
                    break;
                }
                else if(cbData == 0)
                {
                    LOG1( _T("**** Server unexpectedly disconnected\n") );
                    hr = SEC_E_INTERNAL_ERROR;
                    break;
                }
                LOG1( _T("%d bytes of handshake data received\n"), cbData);
                cbIoBuffer += cbData;
            }
            else
                fDoRead = TRUE;
        }
        
        // Set up the input buffers. Buffer 0 is used to pass in data received from the server. Schannel will consume some or all of this.
        // Leftover data (if any) will be placed in buffer 1 and given a buffer type of SECBUFFER_EXTRA.
        InBuffers[0].pvBuffer   = IoBuffer;
        InBuffers[0].cbBuffer   = cbIoBuffer;
        InBuffers[0].BufferType = SECBUFFER_TOKEN;
        InBuffers[1].pvBuffer   = NULL;
        InBuffers[1].cbBuffer   = 0;
        InBuffers[1].BufferType = SECBUFFER_EMPTY;
        InBuffer.cBuffers       = 2;
        InBuffer.pBuffers       = InBuffers;
        InBuffer.ulVersion      = SECBUFFER_VERSION;

        // Set up the output buffers. These are initialized to NULL so as to make it less likely we'll attempt to free random garbage later.
        OutBuffers[0].pvBuffer  = NULL;
        OutBuffers[0].BufferType= SECBUFFER_TOKEN;
        OutBuffers[0].cbBuffer  = 0;
        OutBuffer.cBuffers      = 1;
        OutBuffer.pBuffers      = OutBuffers;
        OutBuffer.ulVersion     = SECBUFFER_VERSION;

        SEC_WCHAR szHostName[MAX_PATH];
        m_spNetClient->GetRemoteHostName( szHostName );

        // Call InitializeSecurityContext.
        hr = m_pSSPI->InitializeSecurityContext( phCreds, phContext, szHostName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, NULL, &OutBuffer, &dwSSPIOutFlags, &tsExpiry );
        
        // If InitializeSecurityContext was successful (or if the error was one of the special extended ones), send the contends of the output buffer to the server.
        if( hr == SEC_E_OK                ||
            hr == SEC_I_CONTINUE_NEEDED   ||
            FAILED( hr ) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR ) )
        {
            if( OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL )
            {
                cbData = m_spNetClient->SendRaw( (LPBYTE)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer );
                if( cbData == SOCKET_ERROR || cbData == 0 )
                {
                    LOG1( _T("**** Error %d sending data to server (2)\n"),  WSAGetLastError() );
                    m_pSSPI->FreeContextBuffer( OutBuffers[0].pvBuffer );
                    m_pSSPI->DeleteSecurityContext( phContext );
                    return SEC_E_INTERNAL_ERROR;
                }
                LOG1( _T("%d bytes of handshake data sent\n"), cbData );

                // Free output buffer.
                m_pSSPI->FreeContextBuffer( OutBuffers[0].pvBuffer );
                OutBuffers[0].pvBuffer = NULL;
            }
        }
        
        // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE, then we need to read more data from the server and try again.
        if( hr == SEC_E_INCOMPLETE_MESSAGE ) continue;

        // If InitializeSecurityContext returned SEC_E_OK, then the handshake completed successfully.
        if( hr == SEC_E_OK )
        {
            // If the "extra" buffer contains data, this is encrypted application protocol layer stuff. It needs to be saved. The application layer
            // will later decrypt it with DecryptMessage.
            LOG1( _T("Handshake was successful\n") );

            if( InBuffers[1].BufferType == SECBUFFER_EXTRA )
            {
                pExtraData->pvBuffer = LocalAlloc( LMEM_FIXED, InBuffers[1].cbBuffer );
                if(pExtraData->pvBuffer == NULL) { LOG1( _T("**** Out of memory (2)\n") ); return SEC_E_INTERNAL_ERROR; }

                MoveMemory( pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer );

                pExtraData->cbBuffer   = InBuffers[1].cbBuffer;
                pExtraData->BufferType = SECBUFFER_TOKEN;

                LOG1( _T("%d bytes of app data was bundled with handshake data\n"), pExtraData->cbBuffer );
            }
            else
            {
                pExtraData->pvBuffer   = NULL;
                pExtraData->cbBuffer   = 0;
                pExtraData->BufferType = SECBUFFER_EMPTY;
            }
            break; // Bail out to quit
        }

        // Check for fatal error.
        if( FAILED( hr ) )
        { 
            LOG1( _T("**** Error 0x%08X returned by InitializeSecurityContext (2)\n"), hr );
            break;
        }

        // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS, then the server just requested client authentication. 
        if( hr == SEC_I_INCOMPLETE_CREDENTIALS )
        {
            // Busted. The server has requested client authentication and
            // the credential we supplied didn't contain a client certificate.
            // This function will read the list of trusted certificate
            // authorities ("issuers") that was received from the server
            // and attempt to find a suitable client certificate that
            // was issued by one of these. If this function is successful, 
            // then we will connect using the new certificate. Otherwise,
            // we will attempt to connect anonymously (using our current credentials).
            GetNewClientCredentials( phCreds, phContext );

            // Go around again.
            fDoRead = FALSE;
            hr = SEC_I_CONTINUE_NEEDED;
            continue;
        }

        // Copy any leftover data from the "extra" buffer, and go around again.
        if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
        {
            MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer );
            cbIoBuffer = InBuffers[1].cbBuffer;
        }
        else
            cbIoBuffer = 0;
    }

    // Delete the security context in the case of a fatal error.
    if( FAILED( hr ) ) {
        m_pSSPI->DeleteSecurityContext( phContext );
        m_bContextInitialized = FALSE;
    }
    LocalFree( IoBuffer );

    return hr;
}

HRESULT CAPNSClient::PerformClientHandshake( PCredHandle phCreds, LPCTSTR pszServerName, CtxtHandle *phContext, SecBuffer *pExtraData )
{
    SecBufferDesc   OutBuffer;
    SecBuffer       OutBuffers[1];
    DWORD           dwSSPIOutFlags;
    TimeStamp       tsExpiry;
    
    DWORD dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;

    // Initiate a ClientHello message and generate a token.
    OutBuffers[0].pvBuffer   = NULL;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = 0;
    OutBuffer.cBuffers       = 1;
    OutBuffer.pBuffers       = OutBuffers;
    OutBuffer.ulVersion      = SECBUFFER_VERSION;

    SEC_WCHAR szHostName[MAX_PATH];
    m_spNetClient->GetRemoteHostName( szHostName );

    HRESULT hr = m_pSSPI->InitializeSecurityContext( phCreds, NULL, szHostName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry );

    if( hr != SEC_I_CONTINUE_NEEDED )
    {
        LOG1( _T("**** Error %d returned by InitializeSecurityContext (1)\n"), hr );
        return hr;
    }

    // Send response to server if there is one.
    if( OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL )
    {
        DWORD cbData = m_spNetClient->SendRaw( (LPBYTE)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer );
        if( cbData == SOCKET_ERROR || cbData == 0 )
        {
            LOG1( _T("**** Error %d sending data to server (1)\n"), WSAGetLastError());
            m_pSSPI->FreeContextBuffer( OutBuffers[0].pvBuffer );
            m_pSSPI->DeleteSecurityContext( phContext );
            return SEC_E_INTERNAL_ERROR;
        }
        LOG1( _T("%d bytes of handshake data sent\n"), cbData );
        m_pSSPI->FreeContextBuffer( OutBuffers[0].pvBuffer ); // Free output buffer.
        OutBuffers[0].pvBuffer = NULL;
    }

    return ClientHandshakeLoop( phCreds, phContext, TRUE, pExtraData );
}

// http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
// The encrypted message is encrypted in place, overwriting the original contents of its buffer.
HRESULT CAPNSClient::EncryptSend( LPBYTE pBuffer, DWORD dwMsgLen ) 
{
    LPBYTE pbMessage = pBuffer + m_StreamSizes.cbHeader; // Offset by "header size"
    LOG1( _T("Sending %d bytes of data:"), dwMsgLen );

    SecBuffer Buffers[4];
    // Encrypt the HTTP request.
    Buffers[0].pvBuffer     = pBuffer;                 // Pointer to buffer 1
    Buffers[0].cbBuffer     = m_StreamSizes.cbHeader;     // length of header
    Buffers[0].BufferType   = SECBUFFER_STREAM_HEADER;    // Type of the buffer 

    Buffers[1].pvBuffer     = pbMessage;                  // Pointer to buffer 2
    Buffers[1].cbBuffer     = dwMsgLen;                  // length of the message
    Buffers[1].BufferType   = SECBUFFER_DATA;             // Type of the buffer 

    Buffers[2].pvBuffer     = pbMessage + dwMsgLen;      // Pointer to buffer 3
    Buffers[2].cbBuffer     = m_StreamSizes.cbTrailer;    // length of the trailor
    Buffers[2].BufferType   = SECBUFFER_STREAM_TRAILER;   // Type of the buffer 

    Buffers[3].pvBuffer     = SECBUFFER_EMPTY;            // Pointer to buffer 4
    Buffers[3].cbBuffer     = SECBUFFER_EMPTY;            // length of buffer 4
    Buffers[3].BufferType   = SECBUFFER_EMPTY;            // Type of the buffer 4 

    SecBufferDesc Message;
    Message.ulVersion       = SECBUFFER_VERSION;          // Version number
    Message.cBuffers        = 4;                          // Number of buffers - must contain four SecBuffer structures.
    Message.pBuffers        = Buffers;                    // Pointer to array of buffers

    HRESULT hr; 
    if( FAILED( hr = m_pSSPI->EncryptMessage( &m_hContext, 0, &Message, 0 ) ) ) // must contain four SecBuffer structures.
    {
        LOG1( _T("**** Error 0x%08X returned by EncryptMessage\n"), hr );
        return hr;
    }
    
    // Send the encrypted data to the server.
    DWORD cbData = m_spNetClient->SendRaw( pBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer );
    if( cbData == SOCKET_ERROR )
    {
        hr = HRESULT_FROM_WIN32( WSAGetLastError() );
    } else {
        LOG3( _T("%d bytes of encrypted data sent\n"), cbData );
        hr = S_OK;
    }
    
    return hr;
}

// calls recv() - blocking socket read
// http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
// The encrypted message is decrypted in place, overwriting the original contents of its buffer.
// http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
HRESULT CAPNSClient::ReadDecrypt( LPBYTE pBuffer, DWORD dwBufLen ) 
{
    SecBuffer ExtraBuffer;
    DWORD cbIoBuffer = 0;
    HRESULT hr = S_OK;

    while( TRUE ) // Read data from server until done.
    {
        if( cbIoBuffer == 0 || hr == SEC_E_INCOMPLETE_MESSAGE ) // get the data
        {
            DWORD cbData = m_spNetClient->ReceiveRaw( pBuffer + cbIoBuffer, dwBufLen - cbIoBuffer );
            if( cbData == SOCKET_ERROR )
            {
                LOG1( _T("**** Error %d reading data from server\n"), WSAGetLastError() );
                return SEC_E_INTERNAL_ERROR;
            }
            else if( cbData == 0 ) // Server disconnected.
            {
                if( cbIoBuffer )
                {
                    LOG1( _T("**** Server unexpectedly disconnected\n") );
                    return SEC_E_INTERNAL_ERROR;
                }
                else
                    break; // All Done
            }
            else // success
            {
                LOG1( _T("%d bytes of (encrypted) application data received\n"), cbData );
                cbIoBuffer += cbData;
            }
        }

        // Decrypt the received data. 
        SecBuffer Buffers[4];
        Buffers[0].pvBuffer     = pBuffer;
        Buffers[0].cbBuffer     = cbIoBuffer;
        Buffers[0].BufferType   = SECBUFFER_DATA;  // Initial Type of the buffer 1
        Buffers[1].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 2 
        Buffers[2].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 3 
        Buffers[3].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 4 

        SecBufferDesc Message;
        Message.ulVersion       = SECBUFFER_VERSION;    // Version number
        Message.cBuffers        = 4;                    // Number of buffers - must contain four SecBuffer structures.
        Message.pBuffers        = Buffers;              // Pointer to array of buffers

        hr = m_pSSPI->DecryptMessage( &m_hContext, &Message, 0, NULL );
        if( hr == SEC_I_CONTEXT_EXPIRED ) break;        // Server signaled end of session
        if( hr == SEC_E_INCOMPLETE_MESSAGE ) continue;  // Input buffer has partial encrypted record, read more
        if( hr != SEC_E_OK && hr != SEC_I_RENEGOTIATE && hr != SEC_I_CONTEXT_EXPIRED ) 
        {
            LOG1( _T("**** DecryptMessage ") ); 
            ShowSecurityError( (DWORD)hr );
            return hr; 
        }
        
        // Locate data and (optional) extra buffers.
        SecBuffer *pDataBuffer  = NULL;
        SecBuffer *pExtraBuffer = NULL;
        for( int i = 1; i < 4; i++ )
        {
            if( pDataBuffer  == NULL && Buffers[i].BufferType == SECBUFFER_DATA  ) pDataBuffer  = &Buffers[i];
            if( pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA ) pExtraBuffer = &Buffers[i];
        }

        if( pDataBuffer ) // show the decrypted data if needed :)
        {
            DWORD dwLength = pDataBuffer->cbBuffer;
            if( dwLength )
            {
                LPBYTE buf = (LPBYTE)pDataBuffer->pvBuffer;
                LOG1( _T("Decrypted data: %d bytes"), dwLength ); 
            }
        }
        
        if( pExtraBuffer ) // Move any "extra" data to the input buffer.
        {
            MoveMemory( pBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer );
            cbIoBuffer = pExtraBuffer->cbBuffer; // LOG1( _T("cbIoBuffer= %d  \n"), cbIoBuffer);
        }
        else
            cbIoBuffer = 0;

        if( hr == SEC_I_RENEGOTIATE ) // The server wants to perform another handshake sequence.
        {
            LOG1( _T("SERVER REQUESTED RENEGOTIATE!\n") );
            if( FAILED( hr = ClientHandshakeLoop( &m_hClientCreds, &m_hContext, FALSE, &ExtraBuffer ) ) )
                return hr;

            if( ExtraBuffer.pvBuffer ) // Move any "extra" data to the input buffer.
            {
                MoveMemory( pBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer );
                cbIoBuffer = ExtraBuffer.cbBuffer;
            }
        }
    }

    return hr;
}

STDMETHODIMP CAPNSClient::SendNotification( IPushNotification *pNtfs )
{
    if( !pNtfs )
        return E_POINTER;

    CComPtr<IGMSBuffer> spBuf;
    HRESULT hr = pNtfs->Makeup( 2, &spBuf );
    if( SUCCEEDED( hr ) )
    {
        hr = SendNotificationsRaw( spBuf );
    }

    return hr;
}

HRESULT CAPNSClient::SendNotificationsRaw( IGMSBuffer *pBuf )
{
    if( !pBuf )
        return E_POINTER;
    if( !m_pIoBuffer )
        return E_UNEXPECTED;
    
    DWORD dwSize = pBuf->GetSize();
    if( dwSize > m_StreamSizes.cbMaximumMessage )
        return E_INVALIDARG;

    HRESULT hr = S_OK;

    // before send more notifications check, maybe APNS returned some errors :)
    DWORD dwDataToRead = m_spNetClient->IsDataAvailable();
    if( dwDataToRead )
    {
        if( SUCCEEDED( hr = ReadDecrypt( m_pIoBuffer, m_dwIoBufLen ) ) )
        {

        } else {
            if( hr == SEC_I_CONTEXT_EXPIRED ) // TODO: reinitialize session!
            {

            }
        }
    }

    if( SUCCEEDED( hr ) ) // send more notification if no error...
    {
        // copy all notifications data
        memcpy( m_pIoBuffer + m_StreamSizes.cbHeader, pBuf->GetPtr(), dwSize );
            
        hr = EncryptSend( m_pIoBuffer, dwSize );
    }

    return hr;
}
